﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;

namespace RaisingForce.Raise.Presentation.ViewModel
{
    /// <summary>
    /// Base class for all ViewModel classes in the application.
    /// It provides support for property change notifications 
    /// and has a DisplayName property.  This class is abstract.
    /// </summary>
    public abstract class ViewModelBase :
        INotifyPropertyChanged,
        IDisposable
    {
        #region Constructor

        protected ViewModelBase()
        { }

        #endregion // Constructor

        #region Protected Declarations

        protected T GetNotNullProperty<T>(
            ref T state,
            string propertyName,
            Action<T> newPropertyInitializer = null)
            where T : class, new()
        {
            Contract.Ensures(Contract.Result<T>() != null);
            this.VerifyPropertyName(propertyName);
            if (state == null)
            {
                state = new T();
                if (newPropertyInitializer != null)
                {
                    newPropertyInitializer(state);
                }
                this.OnPropertyChanged(propertyName);
            }
            return state;
        }

        protected void SetProperty<T>(
            ref T state,
            T value,
            string propertyName,
            Action<T> oldPropertyDeinitializer = null,
            Action<T> newPropertyInitializer = null)
        {
            this.VerifyPropertyName(propertyName);
            if (!object.Equals(state, value))
            {
                if (oldPropertyDeinitializer != null)
                {
                    oldPropertyDeinitializer(state);
                }
                state = value;
                if (newPropertyInitializer != null)
                {
                    newPropertyInitializer(state);
                }
                this.OnPropertyChanged(propertyName);
            }
        }

        protected void OnEvent(EventHandler handler)
        {
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        protected void OnEvent<T>(EventHandler<T> handler, T eventArgs)
            where T : EventArgs
        {
            if (handler != null)
            {
                handler(this, eventArgs);
            }
        }

        #endregion

        #region Window Property

        private WindowViewModel window; // = null

        public WindowViewModel Window
        {
            get { return this.window; }
            internal set { this.window = value; }
        }

        #endregion

        #region DisplayName Property

        /// <summary>
        /// Returns the user-friendly name of this object.
        /// Child classes can set this property to a new value,
        /// or override it to determine the value on-demand.
        /// </summary>
        public virtual string DisplayName { get; protected set; }

        #endregion // DisplayName

        #region Debugging Aides

        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This 
        /// method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,  
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;
                if (this.ThrowOnInvalidPropertyName)
                {
                    throw new Exception(msg);
                }
                else
                {
                    Debug.Fail(msg);
                }
            }
        }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might 
        /// override this property's getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        #endregion // INotifyPropertyChanged Members

        #region IDisposable Members

        /// <summary>
        /// Invoked when this object is being removed from the application
        /// and will be subject to garbage collection.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Child classes can override this method to perform 
        /// clean-up logic, such as removing event handlers.
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposing)
            {
                string msg = string.Format(
                    "{0} ({1}) ({2}) Finalized",
                    this.GetType().Name,
                    this.DisplayName,
                    this.GetHashCode());
                Debug.WriteLine(msg);
            }
        }

#if DEBUG
        /// <summary>
        /// Useful for ensuring that ViewModel objects are properly garbage collected.
        /// </summary>
        ~ViewModelBase()
        {
            this.Dispose(false);
        }
#endif

        #endregion // IDisposable Members
    }
}
